﻿// CoreCalc, a spreadsheet core implementation
// ----------------------------------------------------------------------
// Copyright (c) 2006-2009 Peter Sestoft

// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use, copy,
// modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

//  * The above copyright notice and this permission notice shall be
//    included in all copies or substantial portions of the Software.

//  * The software is provided "as is", without warranty of any kind,
//    express or implied, including but not limited to the warranties of
//    merchantability, fitness for a particular purpose and
//    noninfringement.  In no event shall the authors or copyright
//    holders be liable for any claim, damages or other liability,
//    whether in an action of contract, tort or otherwise, arising from,
//    out of or in connection with the software or the use or other
//    dealings in the software.
// ----------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using C5;

namespace CoreCalc.SheetDefinedFunctions
{
  // For representing and parsing signatures of sheet-defined functions
  // as well as .NET Class Library methods

  public abstract class SdfType
  {
    public abstract Type GetDotNetType();

    // Class hierarchy to represent tokens of function and method signatures

    private abstract class SigToken {
      public readonly static SigToken
        LPAR = new LPar(),
        RPAR = new RPar(),
        LBRA = new LBra(),
        EOF = new Eof();
    }

    private class LPar : SigToken { }
    private class RPar : SigToken { }
    private class LBra : SigToken { }
    private class Eof  : SigToken { }

    private class TypenameToken : SigToken {
      public readonly String typename;
      public TypenameToken(String typename)
      {
        this.typename = typename;
      }
      public override string ToString()
      {
        return typename;
      }
    }

    private class ErrorToken : SigToken
    {
      public readonly String message;
      public ErrorToken(String message)
      {
        this.message = message;
      }
    }

    // ParseType: from stream of signature tokens to a Type object.

    public static SdfType ParseType(String signature)
    {
      IEnumerator<SigToken> tokens = Scanner(signature);
      tokens.MoveNext();  // There's at least Eof in the stream
      SdfType res = ParseOneType(tokens);
      if (tokens.Current is Eof)
        return res;
      else
        throw new SigParseException("Extraneous characters in signature");
    }

    // Before the call, tokens.Current is the first token of the type 
    // to parse; and after the call it is the first token after that type.

    private static SdfType ParseOneType(IEnumerator<SigToken> tokens)
    {
      if (tokens.Current is LPar)
      {
        tokens.MoveNext();
        return ParseFunctionSignature(tokens);
      }
      else if (tokens.Current is LBra)
      {
        tokens.MoveNext();
        return ParseArraySignature(tokens);
      }
      else if (tokens.Current is TypenameToken)
      {
        TypenameToken token = tokens.Current as TypenameToken;
        tokens.MoveNext();
        switch (token.typename)
        {
          case "Z": return new SimpleType(typeof(System.Boolean));
          case "C": return new SimpleType(typeof(System.Char));
          case "B": return new SimpleType(typeof(System.SByte));
          case "b": return new SimpleType(typeof(System.Byte));
          case "S": return new SimpleType(typeof(System.Int16));
          case "s": return new SimpleType(typeof(System.UInt16));
          case "I": return new SimpleType(typeof(System.Int32));
          case "i": return new SimpleType(typeof(System.UInt32));
          case "J": return new SimpleType(typeof(System.Int64));
          case "j": return new SimpleType(typeof(System.UInt64));
          case "F": return new SimpleType(typeof(System.Single));
          case "D": case "N": return new SimpleType(typeof(System.Double));
          case "M": return new SimpleType(typeof(System.Decimal));
          case "V": return new SimpleType(Value.type);
          case "W": return new SimpleType(typeof(void));
          case "T": return new SimpleType(typeof(System.String));
          case "O": return new SimpleType(typeof(System.Object));
          default:
            return new SimpleType(Type.GetType(token.typename));
        }
      }
      else if (tokens.Current is Eof)
        throw new SigParseException("Unexpected end of signature");
      else 
        throw new SigParseException("Unexpected token " + tokens.Current);
    }

    private static SdfType ParseFunctionSignature(IEnumerator<SigToken> tokens)
    {
      ArrayList<SdfType> arguments = new ArrayList<SdfType>();
      while (!(tokens.Current is Eof) && !(tokens.Current is RPar)) 
        arguments.Add(ParseOneType(tokens));
      if (tokens.Current is RPar)
        tokens.MoveNext();
      else
        throw new SigParseException("Unexpected end of function signature");
      SdfType returntype = ParseOneType(tokens);
      return new FunctionType(arguments.ToArray(), returntype);
    }

    private static SdfType ParseArraySignature(IEnumerator<SigToken> tokens)
    {
      if (tokens.Current is Eof)
        throw new SigParseException("Unexpected end of function signature");           
      SdfType elementtype = ParseOneType(tokens);
      return new ArrayType(elementtype);
    }

    public class SigParseException : Exception {
      public SigParseException(String message) : base(message) { } 
    }
    
    // Scanner: from signature string to stream of signature tokens

    private static IEnumerator<SigToken> Scanner(String signature)
    {
      int i = 0;
      while (i < signature.Length)
      {
        char ch = signature[i];
        switch (ch)
        {
          case 'Z': case 'C': case 'B': case 'b': case 'S': case 's': 
          case 'I': case 'i': case 'J': case 'j': 
          case 'D': case 'N': case 'F': case 'M': case 'V': case 'W': case 'T': case 'O':
            yield return new TypenameToken(signature.Substring(i, 1));
            break;
          case 'L': // For instance, LSystem.Text.StringBuilder;
            i++;
            int start = i;
            while (i < signature.Length && signature[i] != ';')
              i++;
            // Now signature[i]==';' or i == signature.Length
            if (i < signature.Length)
              yield return new TypenameToken(signature.Substring(start, i - start));
            else
              yield return new ErrorToken("Unterminated class name");
            break;
          case '(':
            yield return SigToken.LPAR;
            break;
          case ')':
            yield return SigToken.RPAR;
            break;
          case '[':
            yield return SigToken.LBRA;
            break;
          default:
            yield return new ErrorToken("Illegal character '" + ch + "'");
            break;
        }
        i++;
      }
      yield return SigToken.EOF;
      yield break;
    }
  } 

  // A simple (non-composite) type such as Double, String, StringBuilder

  public class SimpleType : SdfType {
    public readonly Type type;

    public SimpleType(Type type)
    {
      this.type = type;
    }

    public override Type GetDotNetType()
    {
      return type;
    } 

    public override string ToString()
    {
      return type.ToString();
    }
  }

  // The type of a sheet-defined function, such as Number * Text -> Value

  public class FunctionType : SdfType
  {
    public readonly SdfType[] arguments;
    public readonly SdfType returntype;

    public FunctionType(SdfType[] arguments, SdfType returntype)
    {
      this.arguments = arguments;
      this.returntype = returntype;
    }

    public Type[] ArgumentDotNetTypes()
    {
      Type[] res = new Type[arguments.Length];
      for (int i = 0; i < arguments.Length; i++)
        res[i] = arguments[i].GetDotNetType();
      return res;
    }

    public override Type GetDotNetType()
    {
      throw new Exception("Function type not allowed");
    }

    public override String ToString()
    {
      StringBuilder sb = new StringBuilder();
      sb.Append("(");
      if (arguments.Length > 0)
        sb.Append(arguments[0]);
      for (int i=1; i<arguments.Length; i++)
        sb.Append(" * ").Append(arguments[i]);
      sb.Append(" -> ").Append(returntype).Append(")");
      return sb.ToString();
    }
  }

  // A CLI array type such as String[], 
  // or a spreadsheet array type such as Number array

  public class ArrayType : SdfType
  {
    public readonly SdfType elementtype;

    public ArrayType(SdfType elementtype)
    {
      this.elementtype = elementtype;
    }

    public override Type GetDotNetType()
    {
      return elementtype.GetDotNetType().MakeArrayType();
    }

    public override String ToString()
    {
      return elementtype + "[]";
    }
  }

  // Machinery for EXTERN: calling .NET functions from a spreadsheet

  class ExternalFunction {
    private readonly Fun<Value, Object>[] argConverters;
    private readonly Fun<Object, Value> resConverter;
    public readonly MethodInfo methodInfo;
    private readonly Type[] argTypes;       // Does not include receiver type
    private readonly Object[] argValues;    // Reused from call to call
    private readonly Type resType;          // Result type
    public readonly bool isStatic;
    private readonly Type recType;                      // Receiver type
    private readonly Fun<Value, Object> recConverter;   // Receiver converter 
    public readonly int arity;              // Includes receiver if !isStatic

    // Invariant: argTypes.Length == argConverters.Length == argValues.Length
    // Invariant: if isStatic then arity==argTypes.Length else arity==argTypes.Length+1
    // Invariant: if !isStatic then recType != null and recConverter != null

    // Mapping .NET types to argument and result converters: Value <-> Object
    private static readonly C5.IDictionary<Type,Fun<Value,Object>> toObjectConverter 
      = new HashDictionary<Type, Fun<Value,Object>>();
    private static C5.IDictionary<Type,Fun<Object,Value>> fromObjectConverter 
      = new HashDictionary<Type, Fun<Object,Value>>();
      
    static ExternalFunction()
    {
      // Initialize tables of conversions  Value <-> Object
      toObjectConverter.Add(typeof(System.Int32), NumberValue.ToInt32);
      toObjectConverter.Add(typeof(System.Double), NumberValue.ToDouble);
      toObjectConverter.Add(typeof(System.Boolean), NumberValue.ToBoolean);
      toObjectConverter.Add(typeof(System.String), TextValue.ToString);
      toObjectConverter.Add(typeof(System.Object), Value.ToObject);

      fromObjectConverter.Add(typeof(System.Int32), NumberValue.FromInt32);
      fromObjectConverter.Add(typeof(System.Double), NumberValue.FromDouble);
      fromObjectConverter.Add(typeof(System.Boolean), NumberValue.FromBoolean);
      fromObjectConverter.Add(typeof(System.String), TextValue.FromString);
      fromObjectConverter.Add(typeof(System.Object), ObjectValue.Make);
      fromObjectConverter.Add(typeof(void), Value.MakeVoid);
    }

    // For caching the result of nameAndSignature) lookups
    private static readonly C5.IDictionary<String, ExternalFunction> cache 
     = new HashDictionary<String, ExternalFunction>();

    public static ExternalFunction Make(String nameAndSignature)
    {
      ExternalFunction res;
      if (!cache.Find(nameAndSignature, out res))
      {
        res = new ExternalFunction(nameAndSignature);
        cache.Add(nameAndSignature, res);
      }
      return res;
    }

    private ExternalFunction(String nameAndSignature)
    {
      int firstParen = nameAndSignature.IndexOf('(');
      if (firstParen <= 0 || firstParen == nameAndSignature.Length -1)
        throw new Exception("Ill-formed name and signature");
      isStatic = nameAndSignature[firstParen-1] == '$';
      String name = nameAndSignature.Substring(0, isStatic ? firstParen - 1 : firstParen);
      String signature = nameAndSignature.Substring(firstParen);
      int lastDot = name.LastIndexOf('.');
      if (lastDot <= 0 || lastDot == name.Length - 1)
        throw new Exception("Ill-formed .NET method name");
      String typeName = name.Substring(0, lastDot);
      String methodName = name.Substring(lastDot + 1);
      Type declaringType = Type.GetType(typeName);
      if (declaringType == null)
        throw new Exception("Unknown .NET type");
      FunctionType ft = SdfType.ParseType(signature) as FunctionType;
      if (ft == null)
        throw new Exception("Ill-formed .NET method signature");
      argTypes = ft.ArgumentDotNetTypes();
      resType = ft.returntype.GetDotNetType();
      methodInfo = declaringType.GetMethod(methodName, argTypes);
      if (methodInfo == null)
        throw new Exception("Unknown .NET method");
      argConverters = new Fun<Value, Object>[argTypes.Length];
      for (int i=0; i<argTypes.Length; i++)
        argConverters[i] = toObjectConverter[argTypes[i]];
      resConverter = fromObjectConverter[ft.returntype.GetDotNetType()];
      if (isStatic)
        arity = argTypes.Length;
      else
      {
        arity = argTypes.Length + 1;
        recType = declaringType;
        recConverter = toObjectConverter[recType];
      }
      argValues = new Object[argTypes.Length];   // Allocate once, reuse at calls
    }

    // Called from the EXTERN function applier in interpreted sheets.
    
    public Value Call(Value[] vs) {
      if (vs.Length != arity)
        return ErrorValue.argCountErrorValue;
      Object receiver;
      if (isStatic)
      {
        receiver = null;
        for (int i = 0; i < vs.Length; i++)
          argValues[i] = argConverters[i](vs[i]);
      }
      else
      {
        receiver = recConverter(vs[0]);
        for (int i = 1; i < vs.Length; i++)
          argValues[i-1] = argConverters[i-1](vs[i]);
      }
      return resConverter(methodInfo.Invoke(receiver, argValues));
    }

    // These are used by the SDF code generator

    public Type ArgType(int i)
    {
      return isStatic ? argTypes[i] : i > 0 ? argTypes[i - 1] : recType;
    }

    public Type ResType
    {
      get { return resType; }
    }
  }
}